Plugins

Argo CD allows integrating more config management tools using config management plugins.

Warning

Plugins are granted a level of trust in the Argo CD system, so it is important to implement plugins securely. Argo CD administrators should only install plugins from trusted sources, and they should audit plugins to weigh their particular risks and benefits.

Installing a CMP

There are two ways to install a Config Management Plugin (CMP):

  1. Add the plugin config to the Argo CD ConfigMap. The repo-server container will run your plugin’s commands. This is a good option for a simple plugin that requires only a few lines of code that fit nicely in the Argo CD ConfigMap.
  2. Add the plugin as a sidecar to the repo-server Pod. This is a good option for a more complex plugin that would clutter the Argo CD ConfigMap. A copy of the repository is sent to the sidecar container as a tarball and processed individually per application, which makes it a good option for concurrent processing of monorepos.

Option 1: Configure plugins via Argo CD configmap

The following changes are required to configure a new plugin:

  1. Make sure required binaries are available in argocd-repo-server pod. The binaries can be added via volume mounts or using a custom image (see custom_tools for examples of both).
  2. Register a new plugin in argocd-cm ConfigMap:

    1. data:
    2. configManagementPlugins: |
    3. - name: pluginName
    4. init: # Optional command to initialize application source directory
    5. command: ["sample command"]
    6. args: ["sample args"]
    7. generate: # Command to generate manifests YAML
    8. command: ["sample command"]
    9. args: ["sample args"]
    10. lockRepo: true # Defaults to false. See below.

    The generate command must print a valid YAML or JSON stream to stdout. Both init and generate commands are executed inside the application source directory or in path when specified for the app.

  3. Create an Application which uses your new CMP.

More CMP examples are available in argocd-example-apps.

Repository locking

If your plugin makes use of git (e.g. git crypt), it is advised to set lockRepo to true so that your plugin will have exclusive access to the repository at the time it is executed. Otherwise, two applications synced at the same time may result in a race condition and sync failure.

Option 2: Configure plugin via sidecar

An operator can configure a plugin tool via a sidecar to repo-server. The following changes are required to configure a new plugin:

1. Write the plugin configuration file

Plugins will be configured via a ConfigManagementPlugin manifest located inside the plugin container.

  1. apiVersion: argoproj.io/v1alpha1
  2. kind: ConfigManagementPlugin
  3. metadata:
  4. name: cmp-plugin
  5. spec:
  6. version: v1.0
  7. init:
  8. # Init always happens immediately before generate, but its output is not treated as manifests.
  9. # This is a good place to, for example, download chart dependencies.
  10. command: [sh, -c, 'echo "Initializing..."']
  11. generate:
  12. command: [sh, -c]
  13. args:
  14. - |
  15. echo "{\"kind\": \"ConfigMap\", \"apiVersion\": \"v1\", \"metadata\": { \"name\": \"$ARGOCD_APP_NAME\", \"namespace\": \"$ARGOCD_APP_NAMESPACE\", \"annotations\": {\"Foo\": \"$ARGOCD_ENV_FOO\", \"KubeVersion\": \"$KUBE_VERSION\", \"KubeApiVersion\": \"$KUBE_API_VERSIONS\",\"Bar\": \"baz\"}}}"
  16. # The discovery config is applied to a repository. If every configured discovery tool matches, then the plugin may be
  17. # used to generate manifests for Applications using the repository.
  18. # Only one of fileName, find.glob, or find.command should be specified. If multiple are specified then only the
  19. # first (in that order) is evaluated.
  20. discover:
  21. fileName: "./subdir/s*.yaml"

Note

While the ConfigManagementPlugin looks like a Kubernetes object, it is not actually a custom resource. It only follows kubernetes-style spec conventions.

The generate command must print a valid YAML stream to stdout. Both init and generate commands are executed inside the application source directory.

The discover.fileName is used as glob pattern to determine whether an application repository is supported by the plugin or not.

  1. discover:
  2. find:
  3. command: [sh, -c, find . -name env.yaml]

If discover.fileName is not provided, the discover.find.command is executed in order to determine whether an application repository is supported by the plugin or not. The find command should return a non-error exit code and produce output to stdout when the application source type is supported.

2. Place the plugin configuration file in the sidecar

Argo CD expects the plugin configuration file to be located at /home/argocd/cmp-server/config/plugin.yaml in the sidecar.

If you use a custom image for the sidecar, you can add the file directly to that image.

  1. WORKDIR /home/argocd/cmp-server/config/
  2. COPY plugin.yaml ./

If you use a stock image for the sidecar or would rather maintain the plugin configuration in a ConfigMap, just nest the plugin config file in a ConfigMap under the plugin.yaml key.

  1. apiVersion: v1
  2. kind: ConfigMap
  3. metadata:
  4. name: cmp-plugin
  5. data:
  6. plugin.yaml: |
  7. apiVersion: argoproj.io/v1alpha1
  8. kind: ConfigManagementPlugin
  9. metadata:
  10. name: cmp-plugin
  11. spec:
  12. version: v1.0
  13. init:
  14. command: [sh, -c, 'echo "Initializing..."']
  15. generate:
  16. command: [sh, -c, 'echo "{\"kind\": \"ConfigMap\", \"apiVersion\": \"v1\", \"metadata\": { \"name\": \"$ARGOCD_APP_NAME\", \"namespace\": \"$ARGOCD_APP_NAMESPACE\", \"annotations\": {\"Foo\": \"$ARGOCD_ENV_FOO\", \"KubeVersion\": \"$KUBE_VERSION\", \"KubeApiVersion\": \"$KUBE_API_VERSIONS\",\"Bar\": \"baz\"}}}"']
  17. discover:
  18. fileName: "./subdir/s*.yaml"

3. Register the plugin sidecar

To install a plugin, patch argocd-repo-server to run the CMP container as a sidecar, with argocd-cmp-server as its entrypoint. You can use either off-the-shelf or custom-built plugin image as sidecar image. For example:

  1. containers:
  2. - name: cmp
  3. command: [/var/run/argocd/argocd-cmp-server] # Entrypoint should be Argo CD lightweight CMP server i.e. argocd-cmp-server
  4. image: busybox # This can be off-the-shelf or custom-built image
  5. securityContext:
  6. runAsNonRoot: true
  7. runAsUser: 999
  8. volumeMounts:
  9. - mountPath: /var/run/argocd
  10. name: var-files
  11. - mountPath: /home/argocd/cmp-server/plugins
  12. name: plugins
  13. # Remove this volumeMount if you've chosen to bake the config file into the sidecar image.
  14. - mountPath: /home/argocd/cmp-server/config/plugin.yaml
  15. subPath: plugin.yaml
  16. name: cmp-plugin
  17. # Starting with v2.4, do NOT mount the same tmp volume as the repo-server container. The filesystem separation helps
  18. # mitigate path traversal attacks.
  19. - mountPath: /tmp
  20. name: cmp-tmp
  21. volumes:
  22. - configMap:
  23. name: cmp-plugin
  24. name: cmp-plugin
  25. - emptyDir: {}
  26. name: cmp-tmp

Double-check these items

  1. Make sure to use /var/run/argocd/argocd-cmp-server as an entrypoint. The argocd-cmp-server is a lightweight GRPC service that allows Argo CD to interact with the plugin.
  2. Make sure that sidecar container is running as user 999.
  3. Make sure that plugin configuration file is present at /home/argocd/cmp-server/config/plugin.yaml. It can either be volume mapped via configmap or baked into image.

Environment

CMP commands have access to

  1. The system environment variables (of the repo-server container for argocd-cm plugins or of the sidecar for sidecar plugins)
  2. Standard build environment
  3. Variables in the application spec (References to system and build variables will get interpolated in the variables’ values):
  1. spec:
  2. source:
  3. plugin:
  4. env:
  5. - name: FOO
  6. value: bar
  7. - name: REV
  8. value: test-$ARGOCD_APP_REVISION

Note

The discover.command command only has access to the above environment starting with v2.4.

Before reaching the init.command, generate.command, and discover.command commands, Argo CD prefixes all user-supplied environment variables (#3 above) with ARGOCD_ENV_. This prevents users from directly setting potentially-sensitive environment variables.

If your plugin was written before 2.4 and depends on user-supplied environment variables, then you will need to update your plugin’s behavior to work with 2.4. If you use a third-party plugin, make sure they explicitly advertise support for 2.4.

Using a CMP

If your CMP is defined in the argocd-cm ConfigMap, you can create a new Application using the CLI. Replace <pluginName> with the name configured in argocd-cm.

  1. argocd app create <appName> --config-management-plugin <pluginName>

If your CMP is defined as a sidecar, you must manually define the Application manifest. Do not configure a name field in the plugin section. The plugin will be automatically matched with the Application based on its discovery rules.

  1. apiVersion: argoproj.io/v1alpha1
  2. kind: Application
  3. metadata:
  4. name: guestbook
  5. namespace: argocd
  6. spec:
  7. project: default
  8. source:
  9. repoURL: https://github.com/argoproj/argocd-example-apps.git
  10. targetRevision: HEAD
  11. path: guestbook
  12. plugin:
  13. # For either argocd-cm- or sidecar-installed CMPs, you can pass environment variables to the CMP.
  14. env:
  15. - name: FOO
  16. value: bar

If you don’t need to set any environment variables, you can set an empty plugin section.

  1. plugin: {}

Important

If your sidecar CMP command runs too long, the command will be killed, and the UI will show an error. The CMP server respects the timeouts set by the server.repo.server.timeout.seconds and controller.repo.server.timeout.seconds items in argocd-cm. Increase their values from the default of 60s.

Each CMP command will also independently timeout on the ARGOCD_EXEC_TIMEOUT set for the CMP sidecar. The default is 90s. So if you increase the repo server timeout greater than 90s, be sure to set ARGOCD_EXEC_TIMEOUT on the sidecar.

Note

Each Application can only have one config management plugin configured at a time. If you’re converting an existing plugin configured through the argocd-cm ConfigMap to a sidecar, make sure the discovery mechanism only returns true for Applications that have had their name field in the plugin section of their spec removed.

Debugging a CMP

If you are actively developing a sidecar-installed CMP, keep a few things in mind:

1) If you are mounting plugin.yaml from a ConfigMap, you will have to restart the repo-server Pod so the plugin will pick up the changes. 2) If you have baked plugin.yaml into your image, you will have to build, push, and force a re-pull of that image on the repo-server Pod so the plugin will pick up the changes. If you are using :latest, the Pod will always pull the new image. If you’re using a different, static tag, set imagePullPolicy: Always on the CMP’s sidecar container. 3) CMP errors are cached by the repo-server in Redis. Restarting the repo-server Pod will not clear the cache. Always do a “Hard Refresh” when actively developing a CMP so you have the latest output.

Note

Each Application can only have one config management plugin configured at a time. If you’re converting an existing plugin configured through the argocd-cm ConfigMap to a sidecar, make sure the discovery mechanism only returns true for Applications that have had their name field in the plugin section of their spec removed.

Plugin tar stream exclusions

In order to increase the speed of manifest generation, certain files and folders can be excluded from being sent to your plugin. We recommend excluding your .git folder if it isn’t necessary. Use Go’s filepatch.Match syntax. For example, .git/* to exclude .git folder.

You can set it one of three ways:

  1. The --plugin-tar-exclude argument on the repo server.
  2. The reposerver.plugin.tar.exclusions key if you are using argocd-cmd-params-cm
  3. Directly setting ARGOCD_REPO_SERVER_PLUGIN_TAR_EXCLUSIONS environment variable on the repo server.

For option 1, the flag can be repeated multiple times. For option 2 and 3, you can specify multiple globs by separating them with semicolons.